package com.study.app.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.study.app.domain.AppSystemNotifications;
import com.study.app.domain.AppUser;
import com.study.app.domain.bo.AppUserBo;
import com.study.app.domain.bo.AppUserStudentBo;
import com.study.app.domain.vo.AppUserVo;
import com.study.app.mapper.AppSystemNotificationsMapper;
import com.study.app.mapper.AppUserMapper;
import com.study.app.service.IAppUserService;
import com.study.common.baidu.ai.AiExamineUtils;
import com.study.common.constant.Constants;
import com.study.common.core.domain.PageQuery;
import com.study.common.core.domain.R;
import com.study.common.core.domain.event.LogininforEvent;
import com.study.common.core.domain.model.*;
import com.study.common.core.page.TableDataInfo;
import com.study.common.exception.ServiceException;
import com.study.common.helper.AppLoginHelper;
import com.study.common.utils.*;
import com.study.common.utils.redis.RedisUtils;
import com.study.common.utils.spring.SpringUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

/**
 * App用户Service业务层处理
 *
 * @author study
 * @date 2023-04-19
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class AppUserServiceImpl implements IAppUserService {

    private final AppUserMapper baseMapper;

    private final AiExamineUtils aiExamineUtils;
    private final AppSystemNotificationsMapper appSystemNotificationsMapper;


    @Override
    public void replacePassword(Long userId, AppReplacePasswordBody replacePasswordBody) {
        AppUser appUser = baseMapper.selectById(userId);
        if (!AesGcmUtils.check(replacePasswordBody.getOldPassword(), appUser.getUserPassword())) {
            throw new ServiceException(MessageUtils.message("old.password.incorrect"));
        }

        appUser.setUserPassword(AesGcmUtils.encrypt(replacePasswordBody.getNewPassword()));
        baseMapper.updateById(appUser);
    }

    @Override
    public void setPassword(Long userId, String password) {
        AppUser appUser = new AppUser();
        appUser.setId(userId);
        appUser.setUserPassword(AesGcmUtils.encrypt(password));
        baseMapper.updateById(appUser);
    }

    @Override
    public void logOff(AppLoginUser loginUser, AppSmsCodeBody appSmsCodeBody) {
        if (!loginUser.getUserPhone().equals(AesGcmUtils.encrypt(appSmsCodeBody.getPhone()))) {
            throw new ServiceException(MessageUtils.message("non.current.account.phone"));
        }

        String key = RedisKeyUtils.getAppSmsDeleteCode(appSmsCodeBody.getPhone());
        checkCodeSms(key, appSmsCodeBody.getCode());

        // 记录注销前用户信息
        AppUser appUser = baseMapper.selectById(loginUser.getUserId());
        log.info("用户注销 => user:{} content: {}", loginUser.getUserPhone(), appUser);

        baseMapper.deleteById(loginUser.getUserId());

        // 删除key
        RedisUtils.deleteObject(key);

        // 退出登录
        StpUtil.logout();
        recordLogininfor(loginUser.getUserPhone(),
                Constants.LOGOUT,
                MessageUtils.message("user.logout.success"));
    }

    @Override
    public AppUserVo getUserByCode(AppUserBo bo) {

        if (StringUtils.isBlank(bo.getInvitationCode())) {
            throw new ServiceException(MessageUtils.message("invitation.code.cannot.empty"));
        }

        LambdaQueryWrapper<AppUser> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(AppUser::getInvitationCode, bo.getInvitationCode())
                .last(" limit 1 ");
        AppUserVo appUserVo = baseMapper.selectVoOne(wrapper);

        AppUserVo appUserVo1 = new AppUserVo();
        appUserVo1.setUserNike(appUserVo.getUserNike());
        return appUserVo1;
    }

    @Override
    public String getUserInfo(String userCode) {
        AppUser appUser = baseMapper.selectOne(new LambdaQueryWrapper<AppUser>().eq(AppUser::getUserCode, userCode));
        if (appUser != null) {
            return appUser.getDeviceToken();
        }
        return null;
    }

    @Override
    public Boolean restPassWord(Long userId) {
        AppUserVo userVo = baseMapper.selectVoById(userId);
        return baseMapper.update(null, new LambdaUpdateWrapper<AppUser>()
                .eq(AppUser::getId, userId)
                .set(AppUser::getUserPassword, userVo.getUserPhone())) > 0;
    }

    @Override
    public Boolean setUserDeviceToken(String userCode, String deviceToken) {
        return baseMapper.update(null, new LambdaUpdateWrapper<AppUser>()
                .eq(AppUser::getUserCode, userCode)
                .set(AppUser::getDeviceToken, deviceToken)) > 0;
    }

    @Override
    public void logOffEmail(AppLoginUser loginUser, AppEmailCodeBody appEmailCodeBody) {
        if (!loginUser.getUserEmail().equals(AesGcmUtils.encrypt(appEmailCodeBody.getEmail()))) {
            throw new ServiceException(MessageUtils.message("non.current.account.email"));
        }

        String key = RedisKeyUtils.getAppEmailDeleteCode(appEmailCodeBody.getEmail());
        checkCodeEmail(key, appEmailCodeBody.getCode());

        // 记录注销前用户信息
        AppUser appUser = baseMapper.selectById(loginUser.getUserId());
        log.info("用户注销 => user:{} content: {}", loginUser.getUserPhone(), appUser);

        baseMapper.deleteById(loginUser.getUserId());

        // 删除key
        RedisUtils.deleteObject(key);

        // 退出登录
        StpUtil.logout();
        recordLogininfor(loginUser.getUserPhone(),
                Constants.LOGOUT,
                MessageUtils.message("user.logout.success"));
    }

    @Override
    public void smsExchangePassword( AppForgetPasswordBody appForgetPasswordBody) {
        String encrypt = AesGcmUtils.encrypt(appForgetPasswordBody.getNumber());
        String key = RedisKeyUtils.getAppSmsForgetCode(appForgetPasswordBody.getNumber());
        checkCodeSms(key, appForgetPasswordBody.getCode());

        AppUser user = baseMapper.selectOne(new LambdaQueryWrapper<AppUser>()
                .eq(AppUser::getUserPhone, encrypt));
        user.setUserPassword(AesGcmUtils.encrypt(appForgetPasswordBody.getPassword()));
        baseMapper.updateById(user);

        // 删除key
        RedisUtils.deleteObject(key);
    }

    @Override
    public void emailExchangePassword(Long userId, AppForgetPasswordBody appForgetPasswordBody) {
        String key = RedisKeyUtils.getAppEmailForgetCode(appForgetPasswordBody.getNumber());
        checkCodeEmail(key, appForgetPasswordBody.getCode());

        String encrypt = AesGcmUtils.encrypt(appForgetPasswordBody.getNumber());

        AppUser appUser = baseMapper.selectOne(new LambdaQueryWrapper<AppUser>()
                .eq(AppUser::getUserEmail, encrypt));
        appUser.setUserPassword(AesGcmUtils.encrypt(appForgetPasswordBody.getPassword()));
        baseMapper.updateById(appUser);

        // 删除key
        RedisUtils.deleteObject(key);
    }

    @Override
    public void exchangeMail(Long userId, AppEmailCodeBody appEmailCodeBody) {
        String key = RedisKeyUtils.getAppEmailExchangeNewCode(appEmailCodeBody.getEmail());
        checkCodeEmail(key, appEmailCodeBody.getCode());

        AppUser appUser = baseMapper.selectById(userId);
        appUser.setUserEmail(AesGcmUtils.encrypt(appEmailCodeBody.getEmail()));
        baseMapper.updateById(appUser);

        // 删除key
        RedisUtils.deleteObject(key);
    }

    @Override
    public int updateAppUserStatus(AppUser appUser) {
        return baseMapper.updateById(appUser);
    }

    @Override
    public R<?> getNoReadMessage() {
        AppLoginUser loginUser = AppLoginHelper.getLoginUser();
        Long userId = loginUser.getUserId();
        String userCode = loginUser.getUserCode();
        Long sysMsgCount = appSystemNotificationsMapper.selectCount(new LambdaQueryWrapper<AppSystemNotifications>()
                .eq(userId != null, AppSystemNotifications::getRecipientId, userId)
                .eq(AppSystemNotifications::getIsRead, 0));
        HashMap<String, Long> res = new HashMap<>();
        res.put("msgCount",sysMsgCount);
        return R.ok(res);
    }

    @Override
    public Boolean createStudyAccount(AppUserStudentBo bo, Long parentId) {
        AppUser add = BeanUtil.toBean(bo, AppUser.class);
        add.setParentId(parentId);
        add.setUserRole("STUDENT");
        add.setStudentGrade(bo.getStudentGrade());
        add.setStudentSchool(bo.getStudentSchool());
        add.setLoginAccount(AesGcmUtils.encrypt(bo.getLoginAccount()));
        add.setUserPassword(AesGcmUtils.encrypt(bo.getUserPassword()));
        add.setUserCode(IdUtil.simpleUUID());
        add.setCreateTime(DateUtils.getNowDate());
        return baseMapper.insert(add) > 0;
    }
    @SneakyThrows
    @Override
    public List<AppUser> quertStudentList(Long userId) {
        List<AppUser> studentList = baseMapper.selectList(new LambdaQueryWrapper<AppUser>()
                .eq(AppUser::getParentId,userId));
        for (AppUser appUser : studentList) {
            appUser.setLoginAccount(appUser.getLoginAccount() != null ? AesGcmUtils.decrypt(appUser.getLoginAccount()) : null);
            appUser.setUserPassword(appUser.getUserPassword() != null ? AesGcmUtils.decrypt(appUser.getUserPassword()) : null);
        }
        return studentList;
    }

    @Override
    public Boolean updateStudent(Long studentId, int status) {
        return baseMapper.update(null,new LambdaUpdateWrapper<AppUser>()
                .eq(AppUser::getId,studentId)
                .set(AppUser::getStatus,status))>0;
    }

    @Override
    public Boolean updateStentInfo(AppUserStudentBo bo) {
        AppUser update = BeanUtil.toBean(bo, AppUser.class);
        return baseMapper.updateById(update) > 0;
    }


    /**
     * 记录登录信息
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     */
    private void recordLogininfor(String username, String status, String message) {
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setUsername(username);
        logininforEvent.setStatus(status);
        logininforEvent.setMessage(message);
        logininforEvent.setRequest(ServletUtils.getRequest());
        SpringUtils.context().publishEvent(logininforEvent);
    }

    @Override
    public void exchangePhone(Long userId, AppSmsCodeBody appSmsCodeBody) {
        String key = RedisKeyUtils.getAppRebindCode(appSmsCodeBody.getPhone());
        checkCodeSms(key, appSmsCodeBody.getCode());

        AppUser appUser = baseMapper.selectById(userId);
        appUser.setUserPhone(AesGcmUtils.encrypt(appSmsCodeBody.getPhone()));
        baseMapper.updateById(appUser);

        // 删除key
        RedisUtils.deleteObject(key);
    }

    @Override
    public void bindPhone(Long userId, AppSmsCodeBody appSmsCodeBody) {
        String key = RedisKeyUtils.getAppSmsBindCode(appSmsCodeBody.getPhone());
        checkCodeSms(key, appSmsCodeBody.getCode());

        AppUser appUser = baseMapper.selectById(userId);
        appUser.setUserPhone(AesGcmUtils.encrypt(appSmsCodeBody.getPhone()));
        appUser.setAreaCode(appSmsCodeBody.getAreaCode());
        baseMapper.updateById(appUser);

        // 删除key
        RedisUtils.deleteObject(key);
    }

    private void checkCodeSms(String key, String code) {
        if (!RedisUtils.hasKey(key)) {
            throw new ServiceException(MessageUtils.message("verification.code.phone.expired"));
        }
        String value = RedisUtils.getCacheObject(key);

        if (!code.equals(value)) {
            throw new ServiceException(MessageUtils.message("verification.code.phone.error"));
        }
    }

    private void checkCodeEmail(String key, String code) {
        if (!RedisUtils.hasKey(key)) {
            throw new ServiceException(MessageUtils.message("verification.code.email.expired"));
        }
        String value = RedisUtils.getCacheObject(key);

        if (!code.equals(value)) {
            throw new ServiceException(MessageUtils.message("verification.code.email.error"));
        }
    }

    /**
     * 查询App用户
     */
    @SneakyThrows
    @Override
    public AppUserVo queryById(Long id) {
        AppUserVo userVo = baseMapper.selectVoById(id);
        userVo.setUserPhone(userVo.getUserPhone() != null ? AesGcmUtils.decrypt(userVo.getUserPhone()) : null);
        userVo.setUserEmail(userVo.getUserEmail() != null ? AesGcmUtils.decrypt(userVo.getUserEmail()) : null);
        return userVo;
    }
    /**
     * 查询App用户详细信息
     */
    @Override
    public AppUser queryUserDetail(AppUser user) {
        AppUser appUser = baseMapper.selectOne(new LambdaQueryWrapper<AppUser>()
                .eq(StringUtils.isNotBlank(user.getUserCode()), AppUser::getUserCode, user.getUserCode()));
        return appUser;
    }

    @Override
    public AppUserVo selectQueryById(Long id) {
        AppUserVo userVo = baseMapper.selectVoById(id);
        return userVo;
    }

    /**
     * 查询App用户列表
     */
    @SneakyThrows
    @Override
    public TableDataInfo<AppUserVo> queryPageList(AppUserBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<AppUser> lqw = buildQueryWrapper(bo);
        Page<AppUserVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        for (AppUserVo record : result.getRecords()) {
            record.setUserPhone(record.getUserPhone() != null ? AesGcmUtils.decrypt(record.getUserPhone()) : null);
            record.setUserEmail(record.getUserEmail() != null ? AesGcmUtils.decrypt(record.getUserEmail()) : null);
        }
        return TableDataInfo.build(result);
    }

    /**
     * 查询App用户列表
     */
    @SneakyThrows
    @Override
    public List<AppUserVo> queryList(AppUserBo bo) {
        LambdaQueryWrapper<AppUser> lqw = buildQueryWrapper(bo);
        List<AppUserVo> vos = baseMapper.selectVoList(lqw);
        for (AppUserVo userVo : vos) {
            userVo.setUserPhone(AesGcmUtils.decrypt(userVo.getUserPhone()));
        }
        return vos;
    }

    private LambdaQueryWrapper<AppUser> buildQueryWrapper(AppUserBo bo) {
        LambdaQueryWrapper<AppUser> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(bo.getUserNike()), AppUser::getUserNike, bo.getUserNike());
        lqw.eq(bo.getUserSex() != null, AppUser::getUserSex, bo.getUserSex());
        lqw.eq(bo.getStatus() != null, AppUser::getStatus, bo.getStatus());
        lqw.eq(StringUtils.isNotBlank(bo.getUserPhone()), AppUser::getUserPhone, AesGcmUtils.encrypt(bo.getUserPhone()));
        lqw.eq(StringUtils.isNotBlank(bo.getUserPassword()), AppUser::getUserPassword, bo.getUserPassword());
        lqw.eq(StringUtils.isNotBlank(bo.getUserEmail()), AppUser::getUserEmail, bo.getUserEmail());
        lqw.eq(StringUtils.isNotBlank(bo.getUserProfile()), AppUser::getUserProfile, bo.getUserProfile());
        lqw.eq(StringUtils.isNotBlank(bo.getUserCountry()), AppUser::getUserCountry, bo.getUserCountry());
        lqw.eq(bo.getUserBirthday() != null, AppUser::getUserBirthday, bo.getUserBirthday());
        lqw.eq(StringUtils.isNotBlank(bo.getImgPath()), AppUser::getImgPath, bo.getImgPath());
        lqw.eq(StringUtils.isNotBlank(bo.getImgBackdrop()), AppUser::getImgPath, bo.getImgBackdrop());
        lqw.eq(StringUtils.isNotBlank(bo.getQqOpenid()), AppUser::getQqOpenid, bo.getQqOpenid());
        lqw.eq(StringUtils.isNotBlank(bo.getIphoneId()), AppUser::getIphoneId, bo.getIphoneId());
        lqw.eq(StringUtils.isNotBlank(bo.getWxOpenid()), AppUser::getWxOpenid, bo.getWxOpenid());
        lqw.eq(bo.getAppType() != null, AppUser::getAppType, bo.getAppType());
        lqw.eq(StringUtils.isNotBlank(bo.getLanguageIden()), AppUser::getLanguageIden, bo.getLanguageIden());
        lqw.eq(bo.getMapType() != null, AppUser::getMapType, bo.getMapType());
        lqw.eq(bo.getMileageUnit() != null, AppUser::getMileageUnit, bo.getMileageUnit());
        return lqw;
    }

    /**
     * 新增App用户
     */
    @Override
    public Boolean insertByBo(AppUserBo bo) {
        AppUser add = BeanUtil.toBean(bo, AppUser.class);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改App用户
     */
    @Override
    public Boolean updateByBo(AppUserBo bo) {
        // 对电话、密码、邮箱进行单独加密后，再修改
        if (StringUtils.isNotBlank(bo.getUserPhone())) {
            bo.setUserPhone(AesGcmUtils.encrypt(bo.getUserPhone()));
        }
        if (StringUtils.isNotBlank(bo.getUserPassword())) {
            bo.setUserPassword(AesGcmUtils.encrypt(bo.getUserPassword()));
        }
        if (StringUtils.isNotBlank(bo.getUserEmail())) {
            bo.setUserEmail(AesGcmUtils.encrypt(bo.getUserEmail()));
        }
        AppUser update = BeanUtil.toBean(bo, AppUser.class);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(AppUser entity) {
        //TODO 做一些数据校验,如唯一约束 对用户昵称和简介进行文本内容审核
        if (entity.getUserNike() != null) {
            if (!aiExamineUtils.textContentReview(entity.getUserNike())) {
                throw new ServiceException(MessageUtils.message("system.nickname.illegal"));
            }
        }
        if (entity.getUserProfile() != null) {
            if (!aiExamineUtils.textContentReview(entity.getUserProfile())) {
                throw new ServiceException("当前简介文本内容存在不合规内容！");
            }
        }

        if (entity.getImgPath() != null) {
            if (!aiExamineUtils.imageReview(entity.getImgPath())) {
                throw new ServiceException("当前上传头像图片存在不合规内容！请重新更换");
            }
        }

        if (entity.getImgBackdrop() != null) {
            if (!aiExamineUtils.imageReview(entity.getImgBackdrop())) {
                throw new ServiceException("当前上传背景图片存在不合规内容！请重新更换");
            }
        }
    }

    /**
     * 批量删除App用户
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

}
